﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;

namespace Btstack
{
    public partial class Bluetooth
    {
        public struct ServicesInfo
        {
            public ServicesInfo(GattClientServiceType service)
            {
                this.service = service;
                characteristics = new List<GattClientCharacteristicType>();
            }

            public readonly GattClientServiceType service;
            public readonly List<GattClientCharacteristicType> characteristics;
        }

        private readonly BtstackPacketHandlerType HandleGattClientEventPtr;
        public readonly List<ServicesInfo> Services = new List<ServicesInfo>();
        private readonly List<IntPtr> GattClientValueListeners = new List<IntPtr>();
        private bool SearchServices = true;
        private int ServiceIndex;

        private void HandleGattClientEvent(byte type, ushort channel, IntPtr PacketPtr, ushort size)
        {
            byte[] packet = new byte[size];
            Marshal.Copy(PacketPtr, packet, 0, size);

            GattClientServiceType service;

            var ConnectionHandle = Connections[0];

            switch (HciEventPacketGetType(packet))
            {
                case GATT_EVENT_SERVICE_QUERY_RESULT:
                    GattEventServiceQueryResultGetService(PacketPtr, out service);
                    Services.Add(new ServicesInfo(service));
                    break;

                case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
                    GattEventCharacteristicQueryResultGetCharacteristic(PacketPtr, out GattClientCharacteristicType characteristic);
                    Services[ServiceIndex].characteristics.Add(characteristic);
                    break;

                case GATT_EVENT_QUERY_COMPLETE:
                    if (SearchServices)
                    {
                        SearchServices = false;
                        ServiceIndex = 0;

                        if (Services.Count < 1)
                            return;

                        service = Services[ServiceIndex].service;
                        GattClientDiscoverCharacteristicsForService(HandleGattClientEventPtr, ConnectionHandle, service);
                    }
                    else
                    {
                        ServiceIndex++;

                        if (ServiceIndex < Services.Count)
                        {
                            service = Services[ServiceIndex].service;
                            GattClientDiscoverCharacteristicsForService(HandleGattClientEventPtr, ConnectionHandle, service);
                            break;
                        }
                        OnDiscoverServiceComplete?.Invoke();
                    }
                    break;

                default:
                    break;
            }
        }

        private void GattClientDiscoverPrimaryServices(ushort handle)
        {
            GattClientValueListeners.Clear();
            Services.Clear();

            SearchServices = true;

            GattClientDiscoverPrimaryServices(HandleGattClientEventPtr, handle);
        }

        public void StartScan(ScanType type = ScanType.Active, ushort interval = 0x30, ushort window = 0x30)
        {
            var c = new ControlData(ControlCmd.StartScan);
            c.Params["type"] = type;
            c.Params["interval"] = interval;
            c.Params["window"] = window;

            ControlDatas.Add(c);

            SetEvent(ControlHandle);
        }

        public void StopScan()
        {
            ControlDatas.Add(new ControlData(ControlCmd.StopScan));
            SetEvent(ControlHandle);
        }

        public void Connect(string addr, BdAddrType type)
        {
            var c = new ControlData(ControlCmd.Connect);
            c.Params["addr"] = addr;
            c.Params["type"] = type;

            ControlDatas.Add(c);

            SetEvent(ControlHandle);
        }

        public void Connect(byte[] addr, BdAddrType type)
        {
            var c = new ControlData(ControlCmd.Connect);
            c.Params["addr"] = addr;
            c.Params["type"] = type;

            ControlDatas.Add(c);

            SetEvent(ControlHandle);
        }

        private bool FindGattClientCharacteristic(ushort uuid16, out GattClientCharacteristicType characteristi)
        {
            foreach (var s in Services)
                foreach (var c in s.characteristics)
                    if (c.uuid16 > 0 && c.uuid16 == uuid16)
                    {
                        characteristi = c;
                        return true;
                    }
            characteristi = default;
            return false;
        }

        private bool FindGattClientCharacteristic(byte[] uuid128, out GattClientCharacteristicType characteristic)
        {
            foreach (var s in Services)
                foreach (var c in s.characteristics)
                    if (c.uuid16 == 0 && Enumerable.SequenceEqual(c.uuid128, uuid128))
                    {
                        characteristic = c;
                        return true;
                    }
            characteristic = default;
            return false;
        }

        public byte WriteValueOfCharacteristicWithoutResponse(ushort uuid16, byte[] value)
        {
            if (Connections.Count < 1)
                return 0;

            if (FindGattClientCharacteristic(uuid16, out GattClientCharacteristicType c))
                return WriteValueOfCharacteristicValueHandleWithoutResponse(c.ValueHandle, value);

            return 0;
        }

        public byte WriteValueOfCharacteristicWithoutResponse(byte[] uuid128, byte[] value)
        {
            if (Connections.Count < 1)
                return 0;

            if (FindGattClientCharacteristic(uuid128, out GattClientCharacteristicType c))
                return WriteValueOfCharacteristicValueHandleWithoutResponse(c.ValueHandle, value);

            return 0;
        }

        private byte WriteValueOfCharacteristicValueHandleWithoutResponse(ushort ValueHandle, byte[] value)
        {
            IntPtr ValuePtr = Marshal.AllocHGlobal(value.Length);
            Marshal.Copy(value, 0, ValuePtr, value.Length);

            var ret = GattClientWriteValueOfCharacteristicWithoutResponse(Connections[0], ValueHandle, (ushort)value.Length, ValuePtr);

            Marshal.FreeHGlobal(ValuePtr);

            return ret;
        }

        // IntPtr.Zero for all
        public void StopListeningForCharacteristicValueUpdates(IntPtr notification)
        {
            if (notification == IntPtr.Zero)
            {
                foreach(var n in GattClientValueListeners)
                {
                    GattClientStopListeningForCharacteristicValueUpdates(n);
                    Marshal.FreeHGlobal(n);
                }
                GattClientValueListeners.Clear();
                return;
            }

            GattClientStopListeningForCharacteristicValueUpdates(notification);
            Marshal.FreeHGlobal(notification);

            GattClientValueListeners.Remove(notification);
        }

        public void WriteClientCharacteristicConfiguration(ushort uuid16, ushort configuration)
        {
            if (!FindGattClientCharacteristic(uuid16, out GattClientCharacteristicType characteristic))
                return;

            if (Connections.Count < 1)
                return;

            IntPtr CharacteristicPtr = Marshal.AllocHGlobal(Marshal.SizeOf<GattClientCharacteristicType>());
            Marshal.StructureToPtr(characteristic, CharacteristicPtr, false);

            GattClientWriteClientCharacteristicConfiguration(PacketHandlerPtr, Connections[0], CharacteristicPtr, configuration);

            Marshal.FreeHGlobal(CharacteristicPtr);
        }

        // -1 for all
        public IntPtr ListenForCharacteristicValueUpdates(BtstackPacketHandlerType callback, int uuid16)
        {
            if (Connections.Count < 1)
                return IntPtr.Zero;

            IntPtr CharacteristicPtr = IntPtr.Zero;

            if (uuid16 >= 0)
            {
                if (!FindGattClientCharacteristic((ushort)uuid16, out GattClientCharacteristicType characteristic))
                    return IntPtr.Zero;

                CharacteristicPtr = Marshal.AllocHGlobal(Marshal.SizeOf<GattClientCharacteristicType>());
                Marshal.StructureToPtr(characteristic, CharacteristicPtr, false);
            }

            var notification = Marshal.AllocHGlobal(Marshal.SizeOf<GattClientNotificationType>());

            GattClientValueListeners.Add(notification);

            GattClientListenForCharacteristicValueUpdates(notification, callback, Connections[0], CharacteristicPtr);

            if (CharacteristicPtr != IntPtr.Zero)
                Marshal.FreeHGlobal(CharacteristicPtr);

            return notification;
        }
    }
}
